package httpx

import (
	"bytes"
	"encoding/json"
	"fmt"
	"gitee.com/kristas/booting-go/framework/common/util/lang"
	"io"
	"net/http"
	"net/url"
	"text/template"
	"time"
)

type Option func(req *Request) error

type Request struct {
	Method     string
	URL        *url.URL
	Body       interface{}
	BodyReader io.Reader
	Param      url.Values
	Header     http.Header
	Timeout    time.Duration
}

func NewRequest(method string, URL string, options ...Option) (*Request, error) {
	_url, err := url.Parse(URL)
	if err != nil {
		return nil, err
	}
	r := &Request{
		Method:  method,
		URL:     _url,
		Timeout: time.Second * 3,
	}
	_ = ContentType("application/json; charset=utf-8")(r)
	err = r.WithOption(options...)
	if err != nil {
		return nil, err
	}
	//for _, option := range options {
	//	if err = option(r); err != nil {
	//		return nil, err
	//	}
	//}
	return r, nil
}

func (r *Request) WithOption(options ...Option) error {
	for _, option := range options {
		if err := option(r); err != nil {
			return err
		}
	}
	return nil
}

func (r *Request) build() (*http.Request, error) {
	req, err := http.NewRequest(r.Method, r.URL.String(), r.BodyReader)
	if err != nil {
		return nil, err
	}
	req.Header = r.Header
	return req, nil
}

func (r *Request) Do() (*Response, error) {
	req, err := r.build()
	if err != nil {
		return nil, err
	}
	c := &http.Client{Timeout: r.Timeout}
	response, err := c.Do(req)
	if err != nil {
		return nil, err
	}
	return newResponse(response)
}

const requestCurlTpl = `curl -X '{{ .Method }}' \
  '{{ .URL }}' \
  {{- if .Header }}
  {{- range .Header }}
  -H '{{ . }}' \{{ end }}
  {{- end }}
  {{- if .Body }}
  -d '{{ .Body }}'
  {{- end }}
`

func (r *Request) Curl() string {
	data := map[string]interface{}{
		"Method": r.Method,
		"URL":    r.URL,
	}
	t := template.New("req_info")
	tpl, _ := t.Parse(requestCurlTpl)
	buffer := bytes.NewBuffer(nil)
	if r.Header != nil {
		headers := make([]string, 0)
		for i := range r.Header {
			headers = append(headers, fmt.Sprintf("%s: %s", i, r.Header.Get(i)))
		}
		data["Header"] = headers
	}
	if r.Body != nil {
		d, _ := json.MarshalIndent(r.Body, "", "  ")
		data["Body"] = string(d)
	}
	tpl.Execute(buffer, data)
	return buffer.String()
}

func HttpHeader(header http.Header) Option {
	return func(req *Request) error {
		if req.Header == nil {
			req.Header = make(http.Header)
		}
		for k := range header {
			req.Header.Set(k, header.Get(k))
		}
		return nil
	}
}

func MapHeader(header map[string]string) Option {
	h := make(http.Header)
	for k, v := range header {
		h.Set(k, v)
	}
	return HttpHeader(h)
}

func Header(name, value string) Option {
	return MapHeader(map[string]string{
		name: value,
	})
}

func ContentType(contentType string) Option {
	return Header("Content-Type", contentType)
}

func QueryValues(values url.Values) Option {
	return func(req *Request) error {
		if req.Param == nil {
			req.Param = make(url.Values)
		}
		for k := range values {
			req.Param.Set(k, values.Get(k))
		}
		req.URL.RawQuery = req.Param.Encode()
		return nil
	}
}

func MapQuery(values map[string]interface{}) Option {
	u := make(url.Values)
	for k, v := range values {
		u.Set(k, fmt.Sprintf("%v", v))
	}
	return QueryValues(u)
}

func Query(name, value string) Option {
	return MapQuery(map[string]interface{}{
		name: value,
	})
}

func PathVar(key string, value interface{}) Option {
	return func(req *Request) error {
		uri := lang.NewString(req.URL.String()).Replace(url.PathEscape("{"+key+"}"), fmt.Sprintf("%v", value), 1).String()
		u, err := url.Parse(uri)
		if err != nil {
			return err
		}
		req.URL = u
		return nil
	}
}

func Body(body interface{}) Option {
	return func(req *Request) error {
		req.Body = body
		data, err := json.Marshal(body)
		if err != nil {
			return err
		}
		req.BodyReader = bytes.NewReader(data)
		return nil
	}
}

func Timeout(t time.Duration) Option {
	return func(req *Request) error {
		req.Timeout = t
		return nil
	}
}
